package io.github.mcsdk.ui.components;

import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.narration.NarrationElementOutput;
import net.minecraft.network.chat.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Generic draggable list widget with reordering and deletion support
 *
 * <p>Features:</p>
 * <ul>
 *   <li>Drag-and-drop reordering</li>
 *   <li>Drag to delete zone</li>
 *   <li>Visual feedback and animations</li>
 *   <li>Scrolling support</li>
 *   <li>Custom item rendering</li>
 * </ul>
 *
 * @param <T> The type of items in the list
 * @author MC SDK Team
 * @version 1.0.0
 * @since 1.0.0
 */
public class DraggableListWidget<T> extends AbstractWidget {

    // Layout constants
    private static final int ITEM_HEIGHT = 22;
    private static final int DELETE_ZONE_SIZE = 60;

    // Data and display
    private List<T> items = new ArrayList<>();
    private final Function<T, String> displayNameProvider;
    private final Function<T, String> tooltipProvider;

    // Selection and interaction
    private int selectedIndex = -1;
    private final Consumer<Integer> onSelectionChanged;

    // Drag state
    private int draggingIndex = -1;
    private int hoverIndex = -1;
    private boolean isDragging = false;
    private double dragStartX;
    private double dragStartY;
    private double dragOffsetX = 0;
    private double dragOffsetY = 0;

    // Delete zone
    private boolean isDraggedOutsideForDelete = false;
    private boolean showDeleteZoneHint = false;

    // Scrolling
    private int scrollOffset = 0;

    // Callbacks
    private Consumer<List<T>> onOrderChanged;
    private Consumer<Integer> onItemDeleted;

    // Visual state
    private long lastClickTime = 0;
    private int consecutiveClicks = 0;
    private static final int MAX_CONSECUTIVE_CLICKS = 5;

    /**
     * Creates a new draggable list widget
     *
     * @param x X position
     * @param y Y position
     * @param width Widget width
     * @param height Widget height
     * @param displayNameProvider Function to get display name from item
     * @param onSelectionChanged Callback when selection changes
     */
    public DraggableListWidget(int x, int y, int width, int height,
                                Function<T, String> displayNameProvider,
                                Consumer<Integer> onSelectionChanged) {
        super(x, y, width, height, Component.empty());
        this.displayNameProvider = displayNameProvider;
        this.tooltipProvider = null;
        this.onSelectionChanged = onSelectionChanged;
    }

    /**
     * Creates a new draggable list widget with tooltip support
     *
     * @param x X position
     * @param y Y position
     * @param width Widget width
     * @param height Widget height
     * @param displayNameProvider Function to get display name from item
     * @param tooltipProvider Function to get tooltip from item
     * @param onSelectionChanged Callback when selection changes
     */
    public DraggableListWidget(int x, int y, int width, int height,
                                Function<T, String> displayNameProvider,
                                Function<T, String> tooltipProvider,
                                Consumer<Integer> onSelectionChanged) {
        super(x, y, width, height, Component.empty());
        this.displayNameProvider = displayNameProvider;
        this.tooltipProvider = tooltipProvider;
        this.onSelectionChanged = onSelectionChanged;
    }

    /**
     * Sets the callback for when item order changes
     *
     * @param onOrderChanged Callback receiving new order
     */
    public void setOnOrderChanged(Consumer<List<T>> onOrderChanged) {
        this.onOrderChanged = onOrderChanged;
    }

    /**
     * Sets the callback for when an item is deleted
     *
     * @param onItemDeleted Callback receiving deleted item index
     */
    public void setOnItemDeleted(Consumer<Integer> onItemDeleted) {
        this.onItemDeleted = onItemDeleted;
    }

    /**
     * Updates the list with new items
     *
     * @param items New item list
     */
    public void updateItems(List<T> items) {
        this.items = new ArrayList<>(items);
        if (selectedIndex >= this.items.size()) {
            selectedIndex = -1;
        }
    }

    /**
     * Gets the current items in order
     *
     * @return List of items
     */
    public List<T> getItems() {
        return new ArrayList<>(items);
    }

    /**
     * Sets the selected item index
     *
     * @param index Index to select (-1 for none)
     */
    public void setSelected(int index) {
        if (index >= -1 && index < items.size()) {
            this.selectedIndex = index;
            if (onSelectionChanged != null && index >= 0) {
                onSelectionChanged.accept(index);
            }
        }
    }

    /**
     * Gets the selected item index
     *
     * @return Selected index (-1 if none)
     */
    public int getSelected() {
        return selectedIndex;
    }

    /**
     * Gets the selected item
     *
     * @return Selected item or null if none
     */
    public T getSelectedItem() {
        return selectedIndex >= 0 && selectedIndex < items.size() ? items.get(selectedIndex) : null;
    }

    /**
     * Checks if currently dragging
     *
     * @return true if drag in progress
     */
    public boolean isDragging() {
        return isDragging;
    }

    @Override
    public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) {
        // Emergency drag timeout check
        if (isDragging && System.currentTimeMillis() - lastClickTime > 30000) {
            stopDragging();
            return;
        }

        // Draw background
        graphics.fill(getX() - 2, getY() - 2, getX() + width + 2, getY() + height + 2, 0x80000000);

        // Calculate visible items
        int maxVisible = Math.max(1, height / ITEM_HEIGHT);
        int endIndex = Math.min(scrollOffset + maxVisible, items.size());

        // Render items
        for (int i = scrollOffset; i < endIndex; i++) {
            int itemY = getY() + (i - scrollOffset) * ITEM_HEIGHT;
            renderItem(graphics, i, itemY, mouseX, mouseY);
        }

        // Render drop indicator
        if (isDragging && hoverIndex >= 0 && hoverIndex >= scrollOffset && hoverIndex < endIndex) {
            int indicatorY = getY() + (hoverIndex - scrollOffset) * ITEM_HEIGHT;
            graphics.fill(getX() - 2, indicatorY - 2, getX() + width + 2, indicatorY + 2, 0xFF00AA00);
        }

        // Render delete zone
        if (isDragging) {
            renderDeleteZone(graphics, mouseX, mouseY);
        }

        // Render dragged item
        if (isDragging && draggingIndex >= 0 && draggingIndex < items.size()) {
            renderDraggedItem(graphics, mouseX, mouseY);
        }

        // Render scroll indicator
        if (items.size() > maxVisible) {
            renderScrollIndicator(graphics);
        }
    }

    private void renderItem(GuiGraphics graphics, int index, int itemY, int mouseX, int mouseY) {
        if (index >= items.size()) return;

        T item = items.get(index);
        String displayName = displayNameProvider.apply(item);

        boolean isHovered = mouseX >= getX() && mouseX <= getX() + width &&
                           mouseY >= itemY && mouseY <= itemY + ITEM_HEIGHT;
        boolean isBeingDragged = isDragging && draggingIndex == index;
        boolean isSelected = selectedIndex == index;

        // Background
        int bgColor = isSelected ? 0x80FFFFFF : (isHovered ? 0x40FFFFFF : 0x00000000);
        graphics.fill(getX(), itemY, getX() + width, itemY + ITEM_HEIGHT, bgColor);

        // Text
        int textColor = isBeingDragged ? 0x808080 : 0xFFFFFF;
        graphics.drawString(Minecraft.getInstance().font, displayName, getX() + 5, itemY + 7, textColor);
    }

    private void renderDeleteZone(GuiGraphics graphics, int mouseX, int mouseY) {
        int deleteZoneY = getY() + height;

        if (isDraggedOutsideForDelete) {
            long time = System.currentTimeMillis();
            float pulse = 0.6f + 0.4f * (float) Math.sin(time * 0.01);
            int alpha = (int) (pulse * 200);

            graphics.fill(getX() - 5, deleteZoneY - 5, getX() + width + 5, deleteZoneY + DELETE_ZONE_SIZE + 5,
                         (alpha << 24) | 0xFF0000);

            Component deleteText = Component.literal("⚠ DROP TO DELETE ⚠");
            int textX = getX() + width / 2 - Minecraft.getInstance().font.width(deleteText) / 2;
            int textY = deleteZoneY + DELETE_ZONE_SIZE / 2 - Minecraft.getInstance().font.lineHeight / 2;
            graphics.drawString(Minecraft.getInstance().font, deleteText, textX, textY, 0xFFFFFFFF);

        } else if (showDeleteZoneHint) {
            graphics.fill(getX(), deleteZoneY, getX() + width, deleteZoneY + DELETE_ZONE_SIZE, 0x40666666);

            Component hintText = Component.literal("Drag here to delete");
            int textX = getX() + width / 2 - Minecraft.getInstance().font.width(hintText) / 2;
            int textY = deleteZoneY + DELETE_ZONE_SIZE / 2 - Minecraft.getInstance().font.lineHeight / 2;
            graphics.drawString(Minecraft.getInstance().font, hintText, textX, textY, 0xFF999999);
        }
    }

    private void renderDraggedItem(GuiGraphics graphics, int mouseX, int mouseY) {
        T item = items.get(draggingIndex);
        String displayName = displayNameProvider.apply(item);

        int textWidth = Minecraft.getInstance().font.width(displayName);
        int dragBoxWidth = Math.max(textWidth + 20, Math.min(width - 10, 250));
        int dragBoxHeight = ITEM_HEIGHT;

        int drawX = mouseX;
        int drawY = mouseY;

        // Shadow
        graphics.fill(drawX - dragBoxWidth / 2 + 2, drawY - dragBoxHeight / 2 + 2,
                     drawX + dragBoxWidth / 2 + 2, drawY + dragBoxHeight / 2 + 2, 0x60000000);

        // Background
        graphics.fill(drawX - dragBoxWidth / 2, drawY - dragBoxHeight / 2,
                     drawX + dragBoxWidth / 2, drawY + dragBoxHeight / 2, 0x90444444);

        // Border
        graphics.fill(drawX - dragBoxWidth / 2, drawY - dragBoxHeight / 2,
                     drawX + dragBoxWidth / 2, drawY - dragBoxHeight / 2 + 1, 0xFFFFFFFF);

        // Text
        int textX = drawX - textWidth / 2;
        int textY = drawY - Minecraft.getInstance().font.lineHeight / 2;
        graphics.drawString(Minecraft.getInstance().font, displayName, textX, textY, 0xFFFFFFFF);
    }

    private void renderScrollIndicator(GuiGraphics graphics) {
        int scrollBarX = getX() + width + 5;
        int scrollBarHeight = height - 4;
        int scrollBarY = getY() + 2;

        graphics.fill(scrollBarX, scrollBarY, scrollBarX + 6, scrollBarY + scrollBarHeight, 0x80444444);

        int visibleCount = getVisibleItemCount();
        if (items.size() > visibleCount) {
            float thumbRatio = Math.min(1.0f, (float)visibleCount / items.size());
            int thumbHeight = Math.max(20, (int)(scrollBarHeight * thumbRatio));

            int maxScroll = getMaxScrollOffset();
            float scrollProgress = maxScroll > 0 ? (float)scrollOffset / maxScroll : 0;
            int thumbY = scrollBarY + (int)(scrollProgress * (scrollBarHeight - thumbHeight));

            graphics.fill(scrollBarX + 1, thumbY, scrollBarX + 5, thumbY + thumbHeight, 0xFFAAAAAA);
        }
    }

    @Override
    public boolean mouseClicked(double mouseX, double mouseY, int button) {
        long currentTime = System.currentTimeMillis();

        // Detect event loops
        if (currentTime - lastClickTime < 50) {
            consecutiveClicks++;
            if (consecutiveClicks > MAX_CONSECUTIVE_CLICKS) {
                stopDragging();
                consecutiveClicks = 0;
                return true;
            }
        } else {
            consecutiveClicks = 0;
        }
        lastClickTime = currentTime;

        if (isDragging) {
            return true;
        }

        if (isMouseOver(mouseX, mouseY) && button == 0) {
            int relativeY = (int) (mouseY - getY());
            int clickedIndex = scrollOffset + relativeY / ITEM_HEIGHT;

            if (clickedIndex >= 0 && clickedIndex < items.size()) {
                setSelected(clickedIndex);
                this.dragStartX = mouseX;
                this.dragStartY = mouseY;
                startDragging(clickedIndex);
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
        if (button == 0 && isDragging) {
            this.dragOffsetX = mouseX - dragStartX;
            this.dragOffsetY = mouseY - dragStartY;

            boolean isInDeleteZone = checkIfInDeleteZone(mouseX, mouseY);
            this.isDraggedOutsideForDelete = isInDeleteZone;

            if (!isInDeleteZone) {
                double relativeY = mouseY - getY();
                int newHoverIndex = scrollOffset + (int) (relativeY / ITEM_HEIGHT);
                newHoverIndex = Math.max(0, Math.min(newHoverIndex, items.size() - 1));
                this.hoverIndex = newHoverIndex;
            } else {
                this.hoverIndex = -1;
            }

            return true;
        }
        return false;
    }

    @Override
    public boolean mouseReleased(double mouseX, double mouseY, int button) {
        if (button == 0 && isDragging) {
            boolean inDeleteZone = checkIfInDeleteZone(mouseX, mouseY);

            if (isDraggedOutsideForDelete && inDeleteZone) {
                deleteItem(draggingIndex);
            } else if (hoverIndex >= 0 && hoverIndex != draggingIndex) {
                swapItems(draggingIndex, hoverIndex);
            }

            isDraggedOutsideForDelete = false;
            stopDragging();
            return true;
        }
        return false;
    }

    @Override
    public boolean mouseScrolled(double mouseX, double mouseY, double delta) {
        if (isMouseOver(mouseX, mouseY)) {
            int newOffset = scrollOffset - (int) delta;
            int maxOffset = getMaxScrollOffset();
            scrollOffset = Math.max(0, Math.min(newOffset, maxOffset));
            return true;
        }
        return false;
    }

    @Override
    protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) {
        // Simple narration
    }

    // Helper methods

    private void startDragging(int index) {
        if (index >= 0 && index < items.size()) {
            this.draggingIndex = index;
            this.isDragging = true;
            this.hoverIndex = index;
            this.showDeleteZoneHint = true;
        }
    }

    private void stopDragging() {
        this.isDragging = false;
        this.draggingIndex = -1;
        this.hoverIndex = -1;
        this.dragOffsetX = 0;
        this.dragOffsetY = 0;
        this.showDeleteZoneHint = false;
        this.isDraggedOutsideForDelete = false;
    }

    private void swapItems(int from, int to) {
        if (from >= 0 && from < items.size() && to >= 0 && to < items.size() && from != to) {
            T item = items.remove(from);
            items.add(to, item);

            if (selectedIndex == from) {
                selectedIndex = to;
            } else if (selectedIndex >= Math.min(from, to) && selectedIndex <= Math.max(from, to)) {
                selectedIndex += (from < to) ? -1 : 1;
            }

            if (onOrderChanged != null) {
                onOrderChanged.accept(new ArrayList<>(items));
            }
        }
    }

    private void deleteItem(int index) {
        if (index >= 0 && index < items.size()) {
            items.remove(index);

            if (selectedIndex == index) {
                selectedIndex = -1;
            } else if (selectedIndex > index) {
                selectedIndex--;
            }

            if (onItemDeleted != null) {
                onItemDeleted.accept(index);
            }

            if (onOrderChanged != null) {
                onOrderChanged.accept(new ArrayList<>(items));
            }
        }
    }

    private boolean checkIfInDeleteZone(double mouseX, double mouseY) {
        int parentHeight = Minecraft.getInstance().getWindow().getGuiScaledHeight();
        int safeDeleteZoneTop = Math.min(getY() + height, parentHeight - DELETE_ZONE_SIZE - 10);
        int safeDeleteZoneBottom = Math.min(safeDeleteZoneTop + DELETE_ZONE_SIZE, parentHeight - 5);

        return mouseX >= getX() && mouseX <= getX() + width &&
               mouseY >= safeDeleteZoneTop && mouseY <= safeDeleteZoneBottom;
    }

    private int getVisibleItemCount() {
        return Math.max(1, height / ITEM_HEIGHT);
    }

    private int getMaxScrollOffset() {
        int visibleCount = getVisibleItemCount();
        return Math.max(0, items.size() - visibleCount);
    }

    /**
     * Global event handling for parent containers
     *
     * @param mouseX Mouse X
     * @param mouseY Mouse Y
     * @param button Mouse button
     * @return true if handled
     */
    public boolean handleGlobalMouseReleased(double mouseX, double mouseY, int button) {
        if (isDragging && button == 0) {
            return mouseReleased(mouseX, mouseY, button);
        }
        return false;
    }

    /**
     * Global drag handling for parent containers
     *
     * @param mouseX Mouse X
     * @param mouseY Mouse Y
     * @param button Mouse button
     * @param deltaX Delta X
     * @param deltaY Delta Y
     * @return true if handled
     */
    public boolean handleGlobalMouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) {
        if (isDragging && button == 0) {
            return mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
        }
        return false;
    }
}